Autor: Paulina Tomaszewska
import numpy as np
import pandas as pd
import sklearn
from sklearn.preprocessing import LabelEncoder
data=pd.read_csv("mushrooms.csv")
data.head()
W poprzedniej pracy domowej wykazano, że dla zbioru danych mushroom zastosowanie target encoding skutkuje niskim wynikiem accuracy. Wynik bliski 100% uzyskano stosując one-hot encoding, która jednak znacząco zwiększa wymiarowość danych. W metodzie SHAP wyliczana jest kontrybucja wszystkich zmiennych, przez co analiza w przypadku dużego większenia liczby kolumn poprzez metodę one-hot jest problematyczna. W ramach dyskusji podczas laboratoriów dowiedziałam się, że zastosowanie metody label encoder (tj. przypisanie kategoriom wartości całkowitych), mimo iż niepoprawne pod względem metodologicznym, prowadzi do wysokiej jakości predycji. Z uwagi na to, że metoda one-hot encoding i label encoding prowadzą do podobnych wartości accuracy, jednak druga z nich nie powoduje zwiększonej liczby zmiennych objaśniających, zdecydowano się na drugą metodę.
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y=le.fit_transform(data['class'])
data.drop('class', axis=1, inplace=True)
for col in data.columns:
data[col]=le.fit_transform(data[col])
data.head(5)
from sklearn.model_selection import train_test_split
seed = 70
test_size = 0.2
x_train, x_test, y_train, y_test = train_test_split(data, y, test_size=test_size, random_state=seed)
from sklearn.metrics import accuracy_score
from sklearn.svm import SVC
from xgboost import XGBClassifier
from sklearn.ensemble import RandomForestClassifier
import lightgbm as lgb
def classifier(model):
model=model.fit(x_train, y_train)
accuracy=accuracy_score(model.predict(x_test),y_test)
return accuracy*100, model
acc, model_rf =classifier(RandomForestClassifier(max_depth=6, random_state=8))
print("Accuracy RandomForest: %.2f%%" % (acc))
acc, model_xgb=classifier(XGBClassifier(objective ='binary:logistic'))
print("Accuracy XGBoost: %.2f%%" % (acc))
import lightgbm as lgb
# create dataset for lightgbm
d_train = lgb.Dataset(x_train, label=y_train)
d_test = lgb.Dataset(x_test, label=y_test)
#Train the model
params = {
"max_bin": 512,
"learning_rate": 0.005,
"boosting_type": "gbdt",
"objective": "binary",
"metric": "auc",
"num_leaves": 10,
"verbose": -1,
"min_data": 100,
"boost_from_average": True
}
model_lightgbm = lgb.train(params,d_train, 10000, valid_sets=[d_test], early_stopping_rounds=50, verbose_eval=1000)
import shap
import xgboost
def shap_decomposition(model, tree_based, index):
# load JS visualization code to notebook
shap.initjs()
if tree_based==True:
explainer = shap.TreeExplainer(model, model_output='probability', data=shap.sample(x_test,100))
else:
f = lambda x: model.predict_proba(x)[:,1]
explainer=shap.KernelExplainer(model.predict_proba, shap.sample(x_test, 100))
shap_values = explainer.shap_values(x_test.iloc[index,:])
return explainer, shap_values
def shap_extreme(shap_values, index_needed=True):
if index_needed:
data=shap_values[1]
else:
data=shap_values
display(pd.DataFrame(data=data, index=x_test.columns).sort_values(by=0, ascending=False).head(5).rename(columns={0: "top shap values"}))
display(pd.DataFrame(data=data, index=x_test.columns).sort_values(by=0, ascending=False).tail(5).rename(columns={0: "smallest shap values"}))
example_index=2
example_sample= x_test.iloc[example_index,:]
model_rf.predict_proba(x_test.values[example_index,:].reshape(1,-1))
explainer, shap_values=shap_decomposition(model_rf, True, example_index)
# visualize the one prediction's explanation (use matplotlib=True to avoid Javascript)
display(shap.force_plot(explainer.expected_value[1], shap_values[1],example_sample))
shap_extreme(shap_values)
model_xgb.predict_proba(x_test)[example_index]
explainer, shap_values=shap_decomposition(model_xgb, True, example_index)
shap.initjs()
#plotuje p-stwo klasy 1
display(shap.force_plot(explainer.expected_value, shap_values, x_test.iloc[example_index,:]))
shap_extreme(shap_values, False)
ewentualne problemy z zastosowaniem xgboost: https://evgenypogorelov.com/multiclass-xgb-shap.html
#p-stwo klasy 1
model_lightgbm.predict(x_test)[example_index]
explainer, shap_values=shap_decomposition(model_lightgbm, True,example_index)
shap.initjs()
display(shap.force_plot(explainer.expected_value, shap_values, x_train.iloc[example_index,:]))
shap_extreme(shap_values,False)
shap.initjs()
explainer = shap.TreeExplainer(model_lightgbm, model_output='probability', data=shap.sample(x_test,100))
shap_values=explainer.shap_values(x_test)
shap.force_plot(explainer.expected_value,shap_values[:1000,:], x_test.iloc[:1000,:])
explainer, shap_values=shap_decomposition(model_lightgbm, True,119)
shap.initjs()
display(shap.force_plot(explainer.expected_value, shap_values, x_train.iloc[119,:]))
shap_extreme(shap_values,False)
W przypadku tej obserwacji największy wpływ na końcową predykcję (obniżenie p-stwa) miały zmienne: odor, stalk-root i spore-print-color. Natomiast dla poprzedniej obserwacji najważniejsze były zmienne odor, gill-color i population. Nie było w analizowanym zbiorze obserwacji dla której jedną z najważniejszych zmiennych nie byłoby odor, gill-size bądź gill-color.
Jednocześnie należy zauważyć, że dla poprzedniej obserwacji zmienna odor powodowała wzrost p-stwa, podczas gdy dla obecnie analizowanej obserwacji spowodowała jego obniżenie.
shap.initjs()
explainer = shap.TreeExplainer(model_lightgbm, model_output='probability', data=shap.sample(x_test,100))
shap_values=explainer.shap_values(x_test)
shap.summary_plot(shap_values, x_test)
shap.initjs()
for name in x_test.columns:
shap.dependence_plot(name, shap_values, x_test, display_features=x_test)
from sklearn.linear_model import LogisticRegression
acc, model_logistic=classifier(SVC(probability=True))
print(acc)
explainer, shap_values=shap_decomposition(model_logistic, False, example_index)
shap.initjs()
display(shap.force_plot(explainer.expected_value[1], shap_values[1], example_sample))
shap_extreme(shap_values, True)
explainer, shap_values=shap_decomposition(model_logistic, False, 119)
shap.initjs()
display(shap.force_plot(explainer.expected_value[1], shap_values[1], x_test.iloc[119,:]))
shap_extreme(shap_values, True)
Gdy przeanalizowano wartości SHAP dla obserwacji nr 119 gdy zastosowano model light_gbm to jako najważniejsze uznano zmienne:
Gdy zastosowano model SVM dla tej samej obserwacji, okazało się, że największą kontrybucję miały:
Na czwartym miejscu znalazła się zmienna odor, która tym razem miała kontrybucję o wartości -0.062 (a więc dwa razy mniejszą niż w przypadku klasyfikatora lightgbm)
Narzędzie shap pozwala na tworzenie ciekawych analiz.Analiza dekompozycji dla tej samej zmiennej jednak gdy użyto inne klasyfikatory (choć o bardzo podobnym accuracy) ujawniła odmienne kontrybucje zmiennych, jednak zawsze wśród trzech najważniejszych zmiennych znajdowała się gill-size lub odor. Jednak pozostałe dwie najważniejsze zmienne się różniły i udało się zademostrować taki przykład. Zaobserwowano, że ta sama zmienna w zależności od analizowanej obserwacji może zwiększać bądź zmniejszać predykcję.